/*
 * Copyright (C) 2010 M. Hautle.
 * 
 * This file is part of SimpleEL.
 * 
 * SimpleEL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * SimpleEL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with SimpleEL. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.simpleel;

import ch.msoftch.internal.*;
import ch.simpleel.accessors.*;
import ch.simpleel.internal.*;
import ch.simpleel.internal.asm.*;
import ch.simpleel.internal.reflection.*;
import ch.simpleel.util.*;

/**
 * Manager returning {@link IValueAccessor} for a given purpose.<br>
 * 
 * @author hautle
 */
public class ValueAccessorManager {
    /** The provider to use. */
    private static final IAccessorProvider PROVIDER = new HybridAccessorProvider();

    /** The cached accessors per class. */
    private static final ICache<Class, IValueAccessor[]> ACCESSORS = CacheFactory.createCache(Class.class, IValueAccessor[].class);

    /**
     * Returns an accessor returning the element passed to {@link IValueAccessor#getValue(Object, Object)}.
     * 
     * @param <T> The return type
     * @return An accessor returning the passed value
     */
    @SuppressWarnings("unchecked")
    public static <T> IValueAccessor<T> getSelfAccessor() {
        return SelfAccessor.ME;
    }

    /**
     * Returns an accessor for the specified property.
     * 
     * @param <T> The return type
     * @param c The bean class
     * @param property The property name
     * @return An corresponding accessor
     */
    public static <T> IValueAccessor<T> getPropertyAccessor(Class<?> c, String property) {
        return PROVIDER.getPropertyAccessor(c, property);
    }

    /**
     * Returns an accessor for the given simple EL expression.
     * 
     * @param <T> The return type
     * @param el The simple El expression (see {@link ELHelper})
     * @param returnType The return type of the expression or null if not known
     * @return An corresponding accessor
     */
    public static <T> IValueAccessor<T> getELAccessor(String el, Class<T> returnType) {
        return PROVIDER.getELAccessor(el, returnType);
    }

    /**
     * Returns the value of the specified property.<br>
     * Use {@link #getPropertyAccessor(Class, String)} if the property will be read multiple times.
     * 
     * @param <T> The return type
     * @param bean The bean
     * @param property The property name
     * @param fallback The fallback value
     * @return The value of the propery or the fallback if no such property exists
     * @throws ValueAccessException If the access failed
     */
    public static <T> T getValue(Object bean, String property, T fallback) throws ValueAccessException {
        return PROVIDER.getValue(bean, property, fallback);
    }

    /**
     * Sets the value on the specified property.<br>
     * Use {@link #getPropertyAccessor(Class, String)} if the property will be written multiple times.
     * 
     * @param bean The bean
     * @param property The property name
     * @param value The value to set
     * @return True if the value was set, false otherwise
     * @throws ValueAccessException If the access failed
     */
    public static boolean setValue(Object bean, String property, Object value) throws ValueAccessException {
        return PROVIDER.setValue(bean, property, value);
    }

    /**
     * Returns the value of the passed expression.<br>
     * Use {@link #getELAccessor(String, Class)} if the expression will be read multiple times.
     * 
     * @param <T> The return type
     * @param el The expression
     * @param bean The bean on which the expression gets executed
     * @param undefined The object to return if the result is undefined
     * @return The value of the passed expression on the given bean
     * @throws ValueAccessException If the access failed
     */
    public static <T> T getValueOf(String el, Object bean, T undefined) throws ValueAccessException {
        return ELUtils.getValueOf(el, bean, undefined, true);
    }

    /**
     * Sets the value on the passed bean using the expression to figure out the right location.<br>
     * Use {@link #getELAccessor(String, Class)} if the expression will be written multiple times.
     * 
     * @param el The expression
     * @param bean The bean on which the expression gets executed or null
     * @param value The value to set (may be null)
     * @return True if something was copied
     * @throws ValueAccessException If the access failed
     */
    public static boolean setValueOf(String el, Object bean, Object value) throws ValueAccessException {
        return ELUtils.setValueOf(el, bean, value, true);
    }

    /**
     * Returns the names of a all properties from the given class.
     * 
     * @param c The class
     * @return The property names ordered by their natural ordering
     */
    public static String[] getPropertyNames(Class<?> c) {
        return ReflectionHelper.getPropertyNames(c);
    }

    /**
     * Returns the names of a all mutable properties from the given class.<br>
     * Mutable properties must have a getter and a setter method.
     * 
     * @param c The class
     * @return The property names ordered by their natural ordering
     */
    public static String[] getMutablePropertyNames(Class<?> c) {
        return ReflectionHelper.getMutablePropertyNames(c);
    }

    /**
     * Returns the accessors for all properties of the given class.
     * 
     * @param c The class
     * @return The property accessors ordered by the natural ordering of their names
     */
    public static IValueAccessor[] getPropertyAccessors(Class<?> c) {
        IValueAccessor[] acc = ACCESSORS.get(c);
        if (acc == null) {
            final String[] names = getPropertyNames(c);
            acc = new IValueAccessor[names.length];
            int i = 0;
            for (String name : names)
                acc[i++] = getPropertyAccessor(c, name);
            ACCESSORS.put(c, acc);
        }
        return acc;
    }

    /**
     * Interface describing an provider for this manager.
     * 
     * @author hautle
     */
    public interface IAccessorProvider {
        /**
         * Returns an accessor for the specified property.
         * 
         * @param <T> The return type
         * @param c The bean class
         * @param property The property name
         * @return An corresponding accessor
         */
        <T> IValueAccessor<T> getPropertyAccessor(Class<?> c, String property);

        /**
         * Returns an accessor for the given simple EL expression.
         * 
         * @param <T> The return type
         * @param el The simple El expression (see {@link ELHelper})
         * @param returnType The return type of the expression or null if not known
         * @return An corresponding accessor
         */
        <T> IValueAccessor<T> getELAccessor(String el, Class<T> returnType);

        /**
         * Returns the value of the specified property.
         * 
         * @param <T> The return type
         * @param bean The bean
         * @param property The property name
         * @param fallback The fallback value
         * @return The value of the propery or the fallback if no such property exists
         * @throws ValueAccessException If the access failed
         */
        <T> T getValue(Object bean, String property, T fallback) throws ValueAccessException;

        /**
         * Sets the value on the specified property.
         * 
         * @param bean The bean
         * @param property The property name
         * @param value The value to set
         * @return True if the value was set, false otherwise
         * @throws ValueAccessException If the access failed
         */
        boolean setValue(Object bean, String property, Object value) throws ValueAccessException;
    }

    /**
     * Accessor just returning the passed value.
     * 
     * @author M. Hautle
     */
    private static class SelfAccessor implements IValueAccessor {
        /** Single instance of this accessor. */
        static final SelfAccessor ME = new SelfAccessor();

        /**
         * Hidden constructor.
         */
        private SelfAccessor() {
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String getPath() {
            return "";
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Class<Object> getType() {
            return Object.class;
        }

        /**
         * Returns just the passed element.
         * 
         * @param element The element
         * @param fallback Not used
         * @return The passed element
         */
        @Override
        public Object getValue(Object element, Object fallback) {
            return element;
        }

        /**
         * Does nothing (you can't set a value on yourself :) )
         * 
         * @param element Not used
         * @param value Not used
         * @return false
         */
        @Override
        public boolean setValue(Object element, Object value) {
            return false;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean isReadable() {
            return true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean isWritable() {
            return false;
        }
    }
}
